package bupt.xixi.serializer;

import bupt.xixi.entity.RpcRequest;
import bupt.xixi.entity.RpcResponse;
import bupt.xixi.enumeration.Codec;
import bupt.xixi.exception.SerializeException;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

public class KryoSerializerI implements ICommonSerializer {

    private static final Logger logger = LoggerFactory.getLogger(KryoSerializerI.class);

    // todo: 为什么使用了threadLocal?
    /**
     * 由于Kryo不是线程安全的，每个线程都应该有自己的kryo, Input 和 Output
     * 所以，使用ThreadLocal存放Kryo对象
     */
    // 每次调用get()的方法时，如果取不到值（懒加载未初始化，也可能被remove了！），就会调用这个init方法！
    private static final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(()->{
        Kryo kryo = new Kryo();
        kryo.register(RpcRequest.class);
        kryo.register(RpcResponse.class);
        kryo.setReferences(true); // 默认值为ture, 是否关闭注册行为，关闭之后可能存在序列化问题
        kryo.setRegistrationRequired(false); //默认值为false，是否关闭循环饮用，可以提高性能，但是一般不推荐
        return kryo;
    });

    @Override
    public byte[] serialize(Object obj) {
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             Output output = new Output(byteArrayOutputStream)) {
            Kryo kryo = kryoThreadLocal.get();
            // Object->byte: 将对象序列化为byte序列
            kryo.writeObject(output, obj);
            kryoThreadLocal.remove();
            return output.toBytes();
        } catch (Exception e) {
            logger.error("序列化时发生错误：", e);
            throw new SerializeException(e.getMessage());
        }
    }

    @Override
    public Object deserialize(byte[] bytes, Class<?> clazz) {
        try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
             Input input = new Input(byteArrayInputStream)) {
            Kryo kryo = kryoThreadLocal.get();
            Object o = kryo.readObject(input, clazz);
            kryoThreadLocal.remove();
            return clazz.cast(o);
        } catch (Exception e) {
            logger.error("反序列化失败", e);
            throw new SerializeException(e.getMessage());
        }

    }

    @Override
    public int getCode() {
        return Codec.SerializeType.KYRO.getCode();
    }
}
